﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CommonLib {
    public class Tracer {
        private Screen screen;
        private Scene scene;

        public Tracer(Screen scr, Scene sce) {
            this.screen = scr;
            this.scene = sce;

        }
        public Screen Screen {
            get { return this.screen; }
        }
        public Primitive findNearest(Ray ray, ref double distance, ref double orientation) {
            Primitive nearest = null;

            for (int j = 0; j < scene.CountPrimitive(); j++) {
                Primitive tmp = scene.GetPrimitive(j);
                double currOri;

                currOri = tmp.isIntersect(ray, ref distance);
                if (currOri == Orientation.Nonexistent)
                    continue;
                nearest = tmp;
                orientation = currOri;
            }
            return nearest;
        }
        public Primitive raytrace(Ray ray, ref Vector3c color, ref Vector3c node, int depth) {
            Primitive nearest = null;
            double distance = Double.MaxValue;
            double orientation = Orientation.Nonexistent;
            Vector3c tmpColor = new Vector3c();
            Vector3c tmpNode = new Vector3c();

            if (depth <= 0)
                return null;

            nearest = findNearest(ray, ref distance, ref orientation);
            if (nearest == null) {
                color.set(0, 0, 0);
                return nearest;
            } else if (nearest.Light) {
                color.set(1.0, 1.0, 1.0);
                return nearest;
            } else
                color.set(0, 0, 0);
            node.set(ray.position).mac(distance, ray.direction);

            Vector3c primNormal = nearest.getNormal(node);
            Vector3c reflDirection = new Vector3c(ray.direction).mac(-2.0 * ray.direction.dot(primNormal), primNormal);
            Vector3c reflPosition = new Vector3c(node).mac(DoubleTester.Epsilon, reflDirection);
            Ray reflRay = new Ray(reflPosition, reflDirection);

            // diffuse, specular & shadow
            for (int j = 0; j < scene.CountLight(); j++) {
                Primitive light = scene.GetLight(j);

                Vector3c dir2light = light.getNormal(node).mul(-1.0);
                Vector3c position = new Vector3c(node).mac(DoubleTester.Epsilon, dir2light);
                Ray ray2light = new Ray(position, dir2light);
                double dist2light = Double.MaxValue;
                double shadow = 1.0;
                double eclipsOri = Orientation.Nonexistent;

                // shadow
                if (light.isIntersect(ray2light, ref dist2light) == Orientation.Exterior) {
                    dist2light -= DoubleTester.Epsilon;
                    Primitive closer = findNearest(ray2light, ref dist2light, ref eclipsOri);
                    if (closer != null)
                        shadow = 0.0;
                }

                // diffuse
                if (nearest.Material.diffuse > 0) {
                    double arg = primNormal.dot(dir2light);
                    if (0 < arg)
                        tmpColor.mac(nearest.Material.diffuse * arg * shadow, light.Material.color);
                }

                // specular
                if (nearest.Material.specular > 0) {
                    double arg = dir2light.dot(reflDirection);
                    if (0 < arg)
                        tmpColor.mac(nearest.Material.specular * Math.Pow(arg, 20.0) * shadow, light.Material.color);
                }
            }
            color.add(tmpColor);
            
            // reflection
            if (nearest.Material.reflection > 0) {
                raytrace(reflRay, ref tmpColor, ref tmpNode, depth - 1);
                color.mac(nearest.Material.reflection, tmpColor);
            }

            // refraction
            if (nearest.Material.refraction > 0) {
                primNormal.mul(orientation);
                double nab = (orientation == Orientation.Exterior ?
                    nearest.Material.refrIndex : 1.0 / nearest.Material.refrIndex);
                double cosA = -ray.direction.dot(primNormal);

                double cosB0 = Math.Sqrt(cosA * cosA + nab * nab - 1);
                Vector3c refrDirection = new Vector3c(ray.direction).mac(-(cosB0 + cosA), primNormal).div(nab);
                Vector3c refrPosition = new Vector3c(node).mac(DoubleTester.Epsilon, refrDirection);

                Ray refrRay = new Ray(refrPosition, refrDirection);
                if (cosA > 0 && cosB0 > 0) {
                    raytrace(refrRay, ref tmpColor, ref tmpNode, depth - 1);
                    Vector3c transparency;
                    if (orientation == Orientation.Exterior) {
                        transparency = new Vector3c(nearest.Material.color).mul(-distance * 0.01).calc(Math.Exp);
                        transparency.mul(nearest.Material.refraction);
                    }  else
                        transparency = new Vector3c(1.0, 1.0, 1.0);

                    color.mac(transparency, tmpColor);
                }
            }

            color.mul(nearest.Material.color);
            return nearest;
        }
        public void render() {
            Primitive prim;

            for (int i = 0; i < screen.getSize(); i++) {
                Vector3c color = new Vector3c();
                Vector3c node = new Vector3c();

                Ray ray = screen.getRay(i);
                prim = raytrace(ray, ref color, ref node, 3);
                screen.setPixel(i, color);
            }
        }
    }
}
